﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace BNet_Bot
{
    delegate void ReceivedPacketEventHandler(byte[] recBytes);

    public enum ConnectionType
    {
        Direct = 0,
        Socks4 = 1
    }

    public class SocketConnection
    {
        public ConnectionType connectionType = ConnectionType.Direct;

        public ReceivedPacketEventHandler Event_ReceivedPacket;

        private Socket socket;
        private Thread thread;

        private IPAddress targetIp;
        private IPEndPoint targetIpEndpoint;

        private IPAddress proxyIp;
        private IPEndPoint proxyIpEndpoint;

        private bool doDisconnect = false;

        private bool useProxy = false;
        private bool proxyGranted = false;

        DataBuffer[] sendBuffer = new DataBuffer[16];

        public SocketConnection()
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            thread = new Thread(ConnectionLoop);
        }

        public void Disconnect()
        {
            doDisconnect = true;
        }

        //Direct Connection
        public void Connect(string ipAddress, int port)
        {
            if (IPAddress.TryParse(ipAddress, out targetIp) == false)
                throw new ArgumentException("Invalid IP Address");
            targetIpEndpoint = new IPEndPoint(targetIp, port);
            useProxy = false;
            socket.Connect(targetIpEndpoint);
            if (socket.Connected)
                thread.Start();
            else
                throw new Exception("Unable to connect");
        }

        //Connects with a Socks4 proxy
        public void Connect(string ipAddress, int port, string proxyIpAddress, int proxyPort)
        {
            if(IPAddress.TryParse(proxyIpAddress, out proxyIp) == false)
                throw new ArgumentException("Invalid IP Address");
            proxyIpEndpoint = new IPEndPoint(proxyIp, proxyPort);
            useProxy = true;
            socket.Connect(proxyIpEndpoint);
            if (socket.Connected)
            {
                thread.Start();
                //SOCKS4 "CONNECT"
                DataBuffer s4Buffer = new DataBuffer();
                s4Buffer.AddByte(0x04);
                s4Buffer.AddByte(0x01);
                byte[] portBytes = BitConverter.GetBytes(port);
                for (int i = portBytes.Length - 1; i >= 0; i--)
                    s4Buffer.AddByte(portBytes[i]);
                s4Buffer.AddByteArray(targetIp.GetAddressBytes());
                s4Buffer.AddByte(0x00);
                proxyGranted = false;
                this.Send(s4Buffer);
            }
            else
                throw new Exception("Unable to connect");
        }

        public void Send(byte[] msg)
        {
            if (socket.Connected == false)
                throw new Exception("Socket not connected");
            for(int i = 0; i < 16; i++)
            {
                if (sendBuffer[i] == null)
                {
                    sendBuffer[i] = new DataBuffer();
                    sendBuffer[i].AddByteArray(msg);
                    break;
                }
            }
        }

        public void Send(DataBuffer buffer)
        {
            if (socket.Connected == false)
                throw new Exception("Socket not connected");
            for (int i = 0; i < 16; i++)
            {
                if (sendBuffer[i] == null)
                {
                    sendBuffer[i] = buffer;
                    break;
                }
            }
        }

        public bool Socks4Connected() { return useProxy && proxyGranted; }

        public void ConnectionLoop()
        {
            while (socket.Connected)
            {
                Thread.Sleep(1);
                if (doDisconnect)
                {
                    doDisconnect = false;
                    socket.Disconnect(true);
                    try
                    {
                        thread.Abort();
                    }
                    catch { }
                }

                //Send Packets
                for (int i = 0; i < 16; i++)
                {
                    if (sendBuffer[i] != null)
                    {
                        socket.Send(sendBuffer[i].GetBuffer());
                        sendBuffer[i].Dispose();
                        sendBuffer[i] = null;
                        break;
                    }
                }

                //Receive Packets
                byte[] recBuffer = new byte[256];
                if (socket.Receive(recBuffer) != null)
                {
                    if (useProxy && proxyGranted == false)
                    {
                        if (recBuffer[0] == 90)
                        {
                            //Proxy Request Granted
                            proxyGranted = true;
                        }
                        else
                            throw new Exception("SOCKS4 request denied");
                    }
                    this.Event_ReceivedPacket(recBuffer);
                }
            }
            this.Dispose();
        }

        public void Dispose()
        {
            socket.Disconnect(false);
            socket.Close();
            try
            {
                thread.Abort();
            }
            catch { }
        }
    }
}
